package com.gitee.wsl.doc.prosemirror.model.dom.parser

import com.gitee.wsl.doc.prosemirror.model.attr.Attrs
import com.gitee.wsl.doc.prosemirror.model.content.ContentMatch
import com.gitee.wsl.doc.prosemirror.model.fragment.Fragment
import com.gitee.wsl.doc.prosemirror.model.mark.Mark
import com.gitee.wsl.doc.prosemirror.model.dom.OPT_OPEN_LEFT
import com.gitee.wsl.doc.prosemirror.model.dom.OPT_PRESERVE_WS
import com.gitee.wsl.doc.prosemirror.model.dom.blockTags
import com.gitee.wsl.doc.prosemirror.model.node.Node
import com.gitee.wsl.doc.prosemirror.model.node.NodeType
import com.gitee.wsl.doc.prosemirror.model.node.inline.TextNode

class NodeContext(
    val type: NodeType?,
    val attrs: Attrs?,
    val marks: List<Mark>,
    val solid: Boolean,
    match: ContentMatch?,
    var options: Int
) {
    var match: ContentMatch? = match ?: if ((options and OPT_OPEN_LEFT) != 0) null else type!!.contentMatch
    val content = mutableListOf<Node>()

    // Marks applied to the node's children
    var activeMarks: List<Mark> = Mark.Companion.none

    @Suppress("ReturnCount")
    fun findWrapping(node: Node): List<NodeType>? {
        if (this.match == null) {
            if (this.type == null) return emptyList()
            val fill = this.type.contentMatch.fillBefore(Fragment.Companion.from(node))
            if (fill != null) {
                this.match = this.type.contentMatch.matchFragment(fill)
            } else {
                val start = this.type.contentMatch
                val wrap = start.findWrapping(node.type)
                return if (wrap != null) {
                    this.match = start
                    wrap
                } else {
                    null
                }
            }
        }
        return this.match?.findWrapping(node.type)
    }

    @Suppress("NestedBlockDepth")
    fun finish(openEnd: Boolean): Any { // Node | Fragment
        if ((this.options and OPT_PRESERVE_WS) == 0) { // Strip trailing whitespace
            val last = this.content.lastOrNull()
            val regex = "[ \\t\\r\\n\\u000c]+\$".toRegex()
            if (last != null && last.isText) {
                val m = regex.find(last.text!!)
                if (m != null) {
                    val text = last as TextNode
                    if (last.text.length == m.groupValues.first().length) {
                        this.content.removeLast()
                    } else {
                        this.content[this.content.size - 1] =
                            text.withText(
                                text.text.slice(0 until text.text.length - m.groupValues.first().length)
                            )
                    }
                }
            }
        }
        var content = Fragment.Companion.from(this.content)
        if (!openEnd) {
            this.match?.let {
                content = content.append(it.fillBefore(Fragment.Companion.empty, true)!!)
            }
        }
        return this.type?.create(this.attrs, content, this.marks) ?: content
    }

    @Suppress("ReturnCount")
    fun inlineContext(node: com.fleeksoft.ksoup.nodes.Node): Boolean {
        if (this.type != null) return this.type.inlineContent
        if (this.content.isNotEmpty()) return this.content[0].isInline
        val name = node.parentNode()?.nodeName()?.lowercase() ?: false
        return !blockTags.contains(name)
    }
}